package org.ptkparty.server.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;

import org.ptkparty.client.place.PlayerDto;
import org.ptkparty.server.dao.EMFService;
import org.ptkparty.shared.cons.GoogleApiConst;
import org.ptkparty.shared.dto.GameDto;
import org.ptkparty.shared.dto.ProfilDto;
import org.ptkparty.shared.dto.ResultDto;
import org.ptkparty.shared.model.ChampionshipEntity;
import org.ptkparty.shared.model.GameEntity;
import org.ptkparty.shared.model.PlaceEntity;
import org.ptkparty.shared.model.PtkEnum.RESULT_TYPE;
import org.ptkparty.shared.model.ResultEntity;
import org.ptkparty.shared.model.ScaleEntity;
import org.ptkparty.shared.model.UserEntity;
import org.ptkparty.shared.service.PTKService;

import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.plus.Plus;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.restfb.DefaultFacebookClient;
import com.restfb.FacebookClient;

public class PTKServiceImpl extends RemoteServiceServlet implements PTKService {

	private static final Logger LOGGER = Logger.getLogger(PTKServiceImpl.class.getName());

	private static final HttpTransport httpTransport = new UrlFetchTransport();
	private static final JsonFactory jsonFactory = new JacksonFactory();

	private static final long serialVersionUID = -93209392486983977L;

	public PlayerDto logUser() throws Exception {
		try {
			UserService userService = UserServiceFactory.getUserService();

			if (userService.isUserLoggedIn()) {
				User user = userService.getCurrentUser();

				UserEntity userEntity = UserEntity.updateUser(user);

				PlayerDto playerDto = new PlayerDto();

				playerDto.setAdmin(userService.isUserAdmin());
				playerDto.setCover(userEntity.getCover());
				playerDto.setEmail(userEntity.getEmail());
				playerDto.setId(userEntity.getId());
				playerDto.setImage(userEntity.getImage());
				playerDto.setNickname(userEntity.getNickname());

				playerDto.setLogoutUrl(userService.createLogoutURL("/"));

				return playerDto;
			}
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
		return null;
	}

	@Override
	public List<ResultDto> getResults(String championshipId) throws Exception {
		try {
			List<ResultDto> ret = new LinkedList<ResultDto>();
			for (String user : ChampionshipEntity.findChampionshipEntity(championshipId).getUsers()) {
				ResultEntity resultEntity = ResultEntity.findLast(championshipId, user);

				if (resultEntity != null) {
					ResultDto resultDto = new ResultDto();

					resultDto.setFannyLooseGames(resultEntity.getFannyLooseGames());
					resultDto.setFannyWinGames(resultEntity.getFannyWinGames());
					resultDto.setGameNumber(resultEntity.getGameNumber());
					resultDto.setLooseGames(resultEntity.getLooseGames());
					resultDto.setWinGames(resultEntity.getWinGames());
					resultDto.setPoints(resultEntity.getPoints());

					UserEntity userEntity = UserEntity.findUserEntity(resultEntity.getUser());
					resultDto.setUser(userEntity.getNickname());
					resultDto.setUserImage(userEntity.getImage());
					resultDto.setUserId(userEntity.getId());

					ResultEntity secondToLast = ResultEntity.findSecondToLast(championshipId, user);
					if (secondToLast != null) {
						resultDto.setLastPoints(secondToLast.getPoints());
						resultDto.setLastWinGames(secondToLast.getWinGames());
						resultDto.setLastLooseGames(secondToLast.getLooseGames());
					} else {
						resultDto.setLastPoints(resultDto.getPoints());
						resultDto.setLastWinGames(resultDto.getWinGames());
						resultDto.setLastLooseGames(resultDto.getLooseGames());
					}

					ret.add(resultDto);
				}
			}

			Collections.sort(ret, new Comparator<ResultDto>() {

				@Override
				public int compare(ResultDto o1, ResultDto o2) {
					int ret = o2.getPoints().compareTo(o1.getPoints());
					if (ret == 0) {
						return ((Integer) (o2.getWinGames() - o2.getLooseGames())).compareTo(o1.getWinGames() - o1.getLooseGames());
					} else {
						return ret;
					}
				}
			});

			int i = 1;
			for (ResultDto resultDto : ret) {
				resultDto.setPosition(i++);
			}

			Collections.sort(ret, new Comparator<ResultDto>() {

				@Override
				public int compare(ResultDto o1, ResultDto o2) {
					int ret = o2.getLastPoints().compareTo(o1.getLastPoints());
					if (ret == 0) {
						return ((Integer) (o2.getLastWinGames() - o2.getLastLooseGames())).compareTo(o1.getLastWinGames() - o1.getLastLooseGames());
					} else {
						return ret;
					}
				}
			});

			i = 1;
			for (ResultDto resultDto : ret) {
				resultDto.setLastPosition(i++);
			}

			Collections.sort(ret, new Comparator<ResultDto>() {

				@Override
				public int compare(ResultDto o1, ResultDto o2) {
					return o1.getPosition().compareTo(o2.getPosition());
				}
			});

			return ret;
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
	}

	private List<GameDto> getGames(String championshipId, boolean finished) throws Exception {
		try {
			List<GameDto> ret = new ArrayList<GameDto>();

			List<GameEntity> gameEntities;
			if (finished) {
				gameEntities = GameEntity.getAllFinishedGamesForChampionship(championshipId);
			} else {
				gameEntities = GameEntity.getAllGamesForChampionshipOrdered(championshipId);
			}
			if (gameEntities != null) {
				for (GameEntity gameEntity : gameEntities) {
					GameDto gameDto = new GameDto();
					for (String user : gameEntity.getTeamOnePlayers()) {
						if (gameEntity.getScoreTeamOne() > gameEntity.getScoreTeamTwo()) {
							gameDto.getWinner().add(UserEntity.findUserEntity(user).getNickname());
						} else {
							gameDto.getLooser().add(UserEntity.findUserEntity(user).getNickname());
						}
					}
					for (String user : gameEntity.getTeamTwoPlayers()) {
						if (gameEntity.getScoreTeamOne() < gameEntity.getScoreTeamTwo()) {
							gameDto.getWinner().add(UserEntity.findUserEntity(user).getNickname());
						} else {
							gameDto.getLooser().add(UserEntity.findUserEntity(user).getNickname());
						}
					}

					gameDto.setDateCreation(new Date(gameEntity.getDate().getTime()));

					gameDto.setCreator(UserEntity.findUserEntity(gameEntity.getCreator()).getNickname());

					gameDto.setGameType(gameEntity.getGameType());

					gameDto.setResultType(gameEntity.getResultType());

					if (gameEntity.getPlace() != null) {
						gameDto.setPlace(PlaceEntity.findPlaceEntity(gameEntity.getPlace()).getName());
					}

					gameDto.setFinished(gameEntity.getFinished() == null ? true : gameEntity.getFinished());

					gameDto.setId(gameEntity.getId());

					ScaleEntity scale = ScaleEntity.findScaleEntity(gameEntity.getScale());
					gameDto.setScaleWinner(gameEntity.getResultType() == RESULT_TYPE.NORMAL_WIN ? scale.getNormalWin() : scale.getAnormalWin());
					gameDto.setScaleLooser(gameEntity.getResultType() == RESULT_TYPE.NORMAL_WIN ? scale.getNormalLoose() : scale.getAnormalLoose());

					gameDto.setScoreWinner(Math.max(gameEntity.getScoreTeamOne(), gameEntity.getScoreTeamTwo()));

					gameDto.setScoreLooser(Math.min(gameEntity.getScoreTeamOne(), gameEntity.getScoreTeamTwo()));
					gameDto.setResults(new ArrayList<ResultDto>());

					if (gameEntity.getResults() != null) {
						for (ResultEntity resultEntity : gameEntity.getResults()) {
							ResultDto resultDto = new ResultDto();
							resultDto.setFannyLooseGames(resultEntity.getFannyLooseGames());
							resultDto.setFannyWinGames(resultEntity.getFannyWinGames());
							resultDto.setGameNumber(resultEntity.getGameNumber());
							resultDto.setPoints(resultEntity.getPoints());
							resultDto.setWinGames(resultEntity.getWinGames());
							resultDto.setLooseGames(resultEntity.getLooseGames());
							resultDto.setUser(UserEntity.findUserEntity(resultEntity.getUser()).getNickname());
							gameDto.addResult(resultDto);
						}
					}
					ret.add(gameDto);

				}
			}

			Collections.sort(ret, new Comparator<GameDto>() {

				@Override
				public int compare(GameDto o1, GameDto o2) {
					return o2.getDateCreation().compareTo(o1.getDateCreation());
				}
			});

			return ret;
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
	}

	@Override
	public boolean logUserOnFacebook(String clientToken) throws Exception {
		try {
			FacebookClient client = new DefaultFacebookClient(clientToken);
			com.restfb.types.User user = client.fetchObject("me", com.restfb.types.User.class);
			return true;
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
	}

	@Override
	public Boolean logUserOnGplus(String token) throws Exception {
		try {
			GoogleCredential credential = new GoogleCredential.Builder().setJsonFactory(jsonFactory) //
					.setTransport(httpTransport) //
					.setClientSecrets(GoogleApiConst.GPLUS_CLIENT_ID, GoogleApiConst.GPLUS_CLIENT_SECRET) //
					.build() //
					.setAccessToken(token);

			// Create a new authorized API client.
			Plus service = new Plus.Builder(httpTransport, jsonFactory, credential).setApplicationName("PTK Party").build();

			UserEntity.updateUser(service.people().get("me").execute(), token);

			return true;
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
	}

	@Override
	public boolean generateGames(String championship, String boulodrome, List<String> players) throws Exception {
		try {
			Random random = new Random();

			while (players.size() != 0) {
				if (players.size() == 6) {
					buildGame(championship, boulodrome, players, random, 3);

				} else if (players.size() >= 4) {
					buildGame(championship, boulodrome, players, random, 2);

				} else {
					players.clear();
				}
			}
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
		return true;
	}

	private void buildGame(String championship, String boulodrome, List<String> players, Random random, int nbPlayer) {
		GameEntity game = new GameEntity();
		game.setChampionship(championship);
		game.setPlace(boulodrome);
		for (int i = 0; i < nbPlayer; i++) {
			int index = random.nextInt(players.size());
			game.addTeamOnePlayer(players.get(index));
			players.remove(index);
		}
		for (int i = 0; i < nbPlayer; i++) {
			int index = random.nextInt(players.size());
			game.addTeamTwoPlayer(players.get(index));
			players.remove(index);
		}
		game.init();
	}

	@Override
	public void deleteGame(String id) {
		GameEntity.delete(id);
	}

	private void incrementMap(String key, Map<String, Integer> map) {
		if (map.containsKey(key)) {
			map.put(key, map.get(key) + 1);
		} else {
			map.put(key, 1);
		}
	}

	@Override
	public ProfilDto getProfileForCurrentPlayer(String currentUser, String championship) {
		EntityManager em = EMFService.get().createEntityManager();

		ProfilDto ret = new ProfilDto();

		List<ResultDto> allResult = new ArrayList<ResultDto>();

		Map<String, Integer> partnersWins = new HashMap<String, Integer>();
		Map<String, Integer> partnersGames = new HashMap<String, Integer>();
		Map<String, Integer> nbWin = new HashMap<String, Integer>();
		Map<String, Integer> nbLoose = new HashMap<String, Integer>();
		Map<String, ResultDto> lastScores = new HashMap<String, ResultDto>();

		try {
			List<GameEntity> games = GameEntity.getAllFinishedGamesForChampionship(championship);

			for (GameEntity game : games) {
				if (game.getTeamOnePlayers().contains(currentUser)) {
					boolean victory = game.getScoreTeamOne() > game.getScoreTeamTwo();
					List<String> partners = new ArrayList<>(game.getTeamOnePlayers());
					partners.remove(currentUser);
					computeStatsForGame(partners, game.getTeamTwoPlayers(), victory, partnersGames, partnersWins, nbLoose, nbWin);
				} else if (game.getTeamTwoPlayers().contains(currentUser)) {
					boolean victory = game.getScoreTeamOne() < game.getScoreTeamTwo();
					List<String> partners = new ArrayList<>(game.getTeamTwoPlayers());
					partners.remove(currentUser);
					computeStatsForGame(partners, game.getTeamOnePlayers(), victory, partnersGames, partnersWins, nbLoose, nbWin);
				}

				if (game.getResults() != null) {
					for (ResultEntity result : game.getResults()) {
						ResultDto resultDto = new ResultDto();
						resultDto.setPoints(result.getPoints());

						UserEntity userEntity = UserEntity.findUserEntity(result.getUser());
						resultDto.setUser(userEntity.getNickname());
						resultDto.setUserId(userEntity.getId());
						resultDto.setUserImage(userEntity.getImage());

						lastScores.put(result.getUser(), resultDto);
						if (currentUser.equals(result.getUser())) {
							resultDto.setGameNumber(result.getGameNumber());
							resultDto.setFannyLooseGames(result.getFannyLooseGames());
							resultDto.setFannyWinGames(result.getFannyWinGames());
							resultDto.setLooseGames(result.getLooseGames());
							resultDto.setWinGames(result.getWinGames());

							allResult.add(resultDto);
						}
					}
				}
			}

			List<ResultDto> victims = new ArrayList<ResultDto>();
			for (Entry<String, Integer> entry : sortByComparator(nbWin, 3).entrySet()) {
				ResultDto resultDto = new ResultDto();
				UserEntity userEntity = UserEntity.findUserEntity(entry.getKey());
				resultDto.setUser(userEntity.getNickname());
				resultDto.setUserId(userEntity.getId());
				resultDto.setUserImage(userEntity.getImage());
				resultDto.setWinGames(entry.getValue().intValue());
				victims.add(resultDto);
			}
			ret.setVictims(victims);

			List<ResultDto> killers = new ArrayList<ResultDto>();
			for (Entry<String, Integer> entry : sortByComparator(nbLoose, 3).entrySet()) {
				ResultDto resultDto = new ResultDto();
				UserEntity userEntity = UserEntity.findUserEntity(entry.getKey());
				resultDto.setUser(userEntity.getNickname());
				resultDto.setUserId(userEntity.getId());
				resultDto.setUserImage(userEntity.getImage());
				resultDto.setLooseGames(entry.getValue().intValue());
				killers.add(resultDto);
			}
			ret.setKillers(killers);

			List<ResultDto> temp = new ArrayList<ResultDto>();
			for (Entry<String, Integer> user : partnersGames.entrySet()) {
				ResultDto resultDto = new ResultDto();

				UserEntity userEntity = UserEntity.findUserEntity(user.getKey());
				resultDto.setUser(userEntity.getNickname());

				Integer nbWins = partnersWins.get(user.getKey());
				resultDto.setUserId(userEntity.getId());
				resultDto.setUserImage(userEntity.getImage());

				if (nbWins == null) {
					nbWins = 0;
				}
				resultDto.setPoints(100 * ((double) nbWins) / user.getValue());
				resultDto.setWinGames(nbWins);

				temp.add(resultDto);
				LOGGER.info("User " + userEntity.getNickname() + " computed : nbWins = " + nbWins + ", nbGames = " + partnersGames + " percent = " + resultDto.getPoints());
			}
			Collections.sort(temp, new Comparator<ResultDto>() {
				@Override
				public int compare(ResultDto o1, ResultDto o2) {
					int res = o2.getPoints().compareTo(o1.getPoints());
					if (res == 0) {
						return o2.getWinGames().compareTo(o1.getWinGames());
					}
					return res;
				}
			});

			ret.setPlayerResults(allResult);

			ret.setLosers(new ArrayList<ResultDto>());
			ret.setWinners(new ArrayList<ResultDto>());

			int nbPartners = Math.min(temp.size(), 6);
			List<ResultDto> additionnalLose = new ArrayList<>();
			List<ResultDto> additionnalWin = new ArrayList<>();

			for (int i = 0; i < nbPartners / 2; i++) {
				if (temp.get(i).getPoints() != 0) {
					ret.getWinners().add(temp.get(i));
				} else {
					additionnalLose.add(temp.get(i));
				}
				int lastIndex = temp.size() - i - 1;
				if (temp.get(lastIndex).getPoints() != 100) {
					ret.getLosers().add(temp.get(lastIndex));
				} else {
					additionnalWin.add(temp.get(lastIndex));
				}

			}

			if (nbPartners % 2 == 1 && nbPartners < 6) {
				ResultDto midResult = temp.get(nbPartners / 2);
				if (midResult.getPoints() >= 50) {
					ret.getWinners().add(midResult);
				} else {
					ret.getLosers().add(midResult);
				}
			}

			if (ret.getWinners().size() < 3) {
				ret.getWinners().addAll(additionnalWin.subList(0, Math.min(additionnalWin.size(), 3 - ret.getWinners().size())));
			}

			if (ret.getLosers().size() < 3) {
				ret.getLosers().addAll(additionnalLose.subList(0, Math.min(additionnalLose.size(), 3 - ret.getLosers().size())));
			}

			Collections.reverse(ret.getLosers());

			List<ResultDto> topPlayers = new ArrayList<ResultDto>(lastScores.values());
			Collections.sort(topPlayers, new Comparator<ResultDto>() {

				@Override
				public int compare(ResultDto o1, ResultDto o2) {
					return o2.getPoints().compareTo(o1.getPoints());
				}
			});

			ret.setTopPlayers(new ArrayList<ResultDto>(topPlayers.subList(0, Math.min(3, topPlayers.size()))));

			return ret;

		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		} finally {
			em.close();
		}
		return null;
	}

	/**
	 * Add informations to the given stats with the given game informations
	 * 
	 * @param partners
	 *            partner's list of users
	 * @param concurrents
	 *            concurrent's list of users
	 * @param victory
	 *            true if the player has won the game
	 * @param nbGames
	 *            Map<String, Integer> games count for each player's partner
	 * @param partnersWins
	 *            Map<String, Integer> wins count for each player's partner
	 * @param killers
	 *            Map<String, Integer> looses count for each player's concurrent
	 * @param victims
	 *            Map<String, Integer> wins count for each player's concurrent
	 */
	private void computeStatsForGame(List<String> partners, List<String> concurrents, boolean victory, Map<String, Integer> nbGames, Map<String, Integer> partnersWins, Map<String, Integer> killers, Map<String, Integer> victims) {

		// Get the concurrent statistics for top killer/victims
		if (victory) {
			for (String otherUser : concurrents) {
				incrementMap(otherUser, victims);
			}
		} else {
			for (String otherUser : concurrents) {
				incrementMap(otherUser, killers);
			}
		}

		// Get the partners statistics for the top/flop partners
		for (String partner : partners) {
			incrementMap(partner, nbGames);
			if (victory) {
				// Win
				incrementMap(partner, partnersWins);
			}
		}
	}

	private static Map<String, Integer> sortByComparator(Map<String, Integer> unsortMap, int retSize) {
		List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(unsortMap.entrySet());

		// sort list based on comparator
		Collections.sort(list, new Comparator<Entry<String, Integer>>() {
			public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
				return o1.getValue().compareTo(o2.getValue());
			}
		});

		// put sorted list into map again
		// LinkedHashMap make sure order in which keys were inserted
		Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
		int i = 0;
		for (Iterator<Entry<String, Integer>> it = list.iterator(); it.hasNext() && i < retSize;) {
			i++;
			Map.Entry<String, Integer> entry = it.next();
			sortedMap.put(entry.getKey(), entry.getValue());
		}
		return sortedMap;
	}

	@Override
	public PlayerDto getUser(String player) throws Exception {
		try {
			UserService userService = UserServiceFactory.getUserService();

			if (userService.isUserLoggedIn()) {
				User user = userService.getCurrentUser();

				UserEntity userEntity = UserEntity.findUserEntity(player);

				PlayerDto playerDto = new PlayerDto();

				playerDto.setAdmin(userService.isUserAdmin());
				playerDto.setCover(userEntity.getCover());
				playerDto.setId(userEntity.getId());
				playerDto.setImage(userEntity.getImage());
				playerDto.setNickname(userEntity.getNickname());

				playerDto.setLogoutUrl(userService.createLogoutURL("/"));

				if (userEntity.getEmail().equalsIgnoreCase(user.getEmail())) {
					// add personal informations
					playerDto.setEmail(userEntity.getEmail());
				}

				return playerDto;
			}
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
		return null;
	}

	@Override
	public boolean regenerateResults(String championshipId) throws Exception {
		try {
			GameEntity.regenerateResults(championshipId);
			return true;
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw e;
		}
	}

	@Override
	public List<GameDto> getFinishedGames(String championshipId) throws Exception {
		return getGames(championshipId, true);
	}

	@Override
	public List<GameDto> getGames(String championshipId) throws Exception {
		return getGames(championshipId, false);
	}
}
